// <copyright file="ManagedInformationCard.cs" company="SharpSTS">
// Copyright (c) 2007, 2008 All Right Reserved, http://sharpsts.com/
//
// This source is subject to the Microsoft Permissive License.
// Please see the License.txt file for more information.
// All other rights reserved.
//
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// </copyright>
// <author>Barry Dorrans</author>
// <email>barryd@idunno.org</email>
// <date>2008-06-13</date>
// <summary>Provides a representation of a managed information card.</summary>

namespace SharpSTS.CardControl
{
    using System;
    using System.Collections.Generic;
    using System.Configuration;
    using System.Drawing.Imaging;
    using System.Globalization;
    using System.IO;
    using System.Security;
    using System.Security.Cryptography.X509Certificates;
    using System.Security.Cryptography.Xml;
    using System.Security.Permissions;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.ServiceModel.Description;
    using System.Text;
    using System.Web;
    using System.Web.Caching;
    using System.Xml;

    using SharpSTS.Configuration;
    using Properties;
    using Providers;

    /// <summary>
    /// Respresents a managed information card.
    /// </summary>
    internal sealed class ManagedInformationCard
    {        
        /// <summary>
        /// The default image used for an information card.
        /// </summary>
        private static readonly byte[] defaultImage;

        /// <summary>
        /// The default claims an information card is populated with.
        /// </summary>
        private static readonly List<ManagedCardClaim> defaultClaims;
        
        /// <summary>
        /// An object to provide thread safe syncronisation.
        /// </summary>
        private static readonly object synclock = new object();

        /// <summary>
        /// The current <see cref="HttpRuntime"/>.
        /// </summary>
        private static HttpRuntime httpRuntime;

        /// <summary>
        /// Initializes the <see cref="ManagedInformationCard"/> class.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage(
            "Microsoft.Performance", 
            "CA1810:InitializeReferenceTypeStaticFieldsInline",
            Justification = "Fields are loaded from configuation files and via code, and cannot be declared in-line.")]
        static ManagedInformationCard()
        {
            // Load the default image and cache it.
            using (MemoryStream ms = new MemoryStream())
            {
                Resources.defaultCardImage.Save(ms, ImageFormat.Jpeg);
                defaultImage = ms.GetBuffer();
                ms.Close();
            }

            // Load the list of supported claims and set those as the default claims for a card.
            defaultClaims = new List<ManagedCardClaim>();
            foreach (SupportedClaim supportedClaim in STSSettings.Settings.SupportedClaims)
            {
                defaultClaims.Add(new ManagedCardClaim(supportedClaim.Uri, supportedClaim.Description));
            }

            // Load and cache the signing key for the card.
            X509Store localMachineCertificateStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
            localMachineCertificateStore.Open(OpenFlags.ReadOnly);
            X509Certificate2Collection certificateCollection = localMachineCertificateStore.Certificates.Find(
                X509FindType.FindByThumbprint,
                STSSettings.Settings.CertificateThumbprint, 
                true);
            SigningCertificate = certificateCollection[0];
            localMachineCertificateStore.Close();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ManagedInformationCard"/> class.
        /// </summary>
        /// <param name="type">Type of the information card it will represent.</param>
        public ManagedInformationCard(AuthenticationType type)
        {
            this.Type = type;

            this.Image = defaultImage;
            this.ImageMimeType = "image/jpeg";
            this.Claims = defaultClaims;
            this.ExpiryDateTime = DateTime.Now.Add(STSSettings.Settings.CardExpiryTimeSpan);
            this.Version = 1;           
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ManagedInformationCard"/> class.
        /// </summary>
        /// <param name="type">Type of the information card it will represent.</param>
        /// <param name="claims">The claims the card will support.</param>
        public ManagedInformationCard(AuthenticationType type, IEnumerable<ManagedCardClaim> claims) : this(type)
        {
            this.Claims.Clear();
            foreach (ManagedCardClaim claim in claims)
            {
                if (string.IsNullOrEmpty(claim.Description))
                {
                    this.Claims.Add(new ManagedCardClaim(claim.Uri, AuthorisationPolicyManager.GetDisplayName(claim.Uri)));
                }
                else
                {
                    this.Claims.Add(claim);
                }
            }
        }

        #region Properties
        /// <summary>
        /// Gets or sets the credential hint.
        /// </summary>
        /// <value>The credential hint.</value>
        public string CredentialHint
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the credential description.
        /// </summary>
        /// <value>The credential description.</value>
        public string CredentialDescription
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the service address.
        /// </summary>
        /// <value>The service address.</value>
        public Uri ServiceAddress
        {
            set;
            get;
        }

        /// <summary>
        /// Gets or sets the mex address.
        /// </summary>
        /// <value>The mex address.</value>
        public Uri MexAddress
        {
            set;
            get;
        }

        /// <summary>
        /// Gets or sets the card expiry date and time.
        /// </summary>
        /// <value>The expiry date and time.</value>
        public DateTime ExpiryDateTime
        {
            get;
            set;
        }

        /// <summary>
        /// Sets the name of the image file.
        /// </summary>
        /// <value>The name of the image file.</value>
        public string ImageFileName
        {
            [FileIOPermission(SecurityAction.Demand)]
            set
            {
                // Check if the file name passed is valid.
                if (!FilePathValidator.IsValid(value))
                {
                    throw new IOException(Resources.ErrorImageFileNameIsInvalid);
                }

                // Build the path to the filename.
                string path;
                if (STSSettings.Settings.CardImageDirectory[STSSettings.Settings.CardImageDirectory.Length - 1] !=
                    Path.DirectorySeparatorChar &&
                    value[0] != Path.DirectorySeparatorChar)
                {
                    path = string.Format(
                        CultureInfo.InvariantCulture,
                        "{0}{1}{2}",
                         STSSettings.Settings.CardImageDirectory,
                         Path.DirectorySeparatorChar,
                         value);
                }
                else
                {
                    path = string.Format(
                        CultureInfo.InvariantCulture,
                        "{0}{1}",
                        STSSettings.Settings.CardImageDirectory,
                        value);
                }

                // Set the mime type for the image, using the file extension.
                FileInfo fileInfo = new FileInfo(path);
                switch (fileInfo.Extension.ToUpperInvariant())
                {
                    case ".JPEG":
                        this.ImageMimeType = "image/jpeg";
                        break;
                    case ".JPG":
                        this.ImageMimeType = "image/jpeg";
                        break;
                    case ".PNG":
                        this.ImageMimeType = "image/png";
                        break;
                    case ".GIF":
                        this.ImageMimeType = "image/gif";
                        break;
                    case ".BMP":
                        this.ImageMimeType = "image/bmp";
                        break;
                    case ".TIFF":
                        this.ImageMimeType = "image/tiff";
                        break;
                    default:
                        throw new BadImageFormatException(string.Format(
                            CultureInfo.InvariantCulture, 
                            Resources.ErrorImageFileFormatNotSupport, 
                            fileInfo.FullName));
                }

                // Now see if we can open it.
                try
                {
                    string imageCacheKey = string.Format(CultureInfo.InvariantCulture, "sharpsts_cardImage_{0}", path);

                    // And cache it 
                    if (null == Cache.Get(imageCacheKey))
                    {
                        FileIOPermission fileIOPermission = new FileIOPermission(FileIOPermissionAccess.Read, path);
                        try
                        {
                            byte[] image;
                            fileIOPermission.Assert();
                            if (!File.Exists(path))
                            {
                                throw new FileNotFoundException(Resources.ErrorImageFileNotFound, path);
                            }

                            using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
                            {
                                image = new byte[fs.Length];
                                fs.Read(image, 0, image.Length);
                                fs.Close();
                            }

                            Cache[imageCacheKey] = image;
                        }
                        finally
                        {
                            CodeAccessPermission.RevertAssert();
                        }
                    }

                    this.Image = Cache[imageCacheKey] as byte[];
                }
                catch (SecurityException ex)
                {
                    throw new SecurityException(Resources.ErrorNoPermissionsToOpenImageFile, ex);
                }
            }
        }

        /// <summary>
        /// Gets or sets the card id.
        /// </summary>
        /// <value>The card id.</value>
        public string Id
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the name of the card.
        /// </summary>
        /// <value>The name of the card.</value>
        public string Name
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the asp.net Cache object.
        /// </summary>
        private static Cache Cache
        {
            get
            {
                if (null == httpRuntime)
                {
                    lock (synclock)
                    {
                        if (null == httpRuntime)
                        {
                            httpRuntime = new HttpRuntime();
                        }
                    }
                }

                return HttpRuntime.Cache;
            }
        }

        /// <summary>
        /// Gets or sets the signing certificate.
        /// </summary>
        /// <value>The signing certificate.</value>
        private static X509Certificate2 SigningCertificate
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the claims the information card will support.
        /// </summary>
        /// <value>The claims the information card will support.</value>
        private List<ManagedCardClaim> Claims
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the authentication type of the managed card.
        /// </summary>
        /// <value>The type of the authentication type card.</value>
        private AuthenticationType Type
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the version of the card.
        /// </summary>
        /// <value>The version of the card.</value>
        private int Version
        { 
            get; 
            set;
        }

        /// <summary>
        /// Gets or sets the image for the managed card.
        /// </summary>
        /// <value>The image.</value>
        private byte[] Image
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the image mime type.
        /// </summary>
        /// <value>The image mime type.</value>
        private string ImageMimeType
        {
            get;
            set;
        }
        #endregion

        #region Methods
        /// <summary>
        /// Gets the signed XML representation of the managed card.
        /// </summary>
        /// <returns>An <see cref="XmlElement"/> containing the signed XML for the card.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage(
            "Microsoft.Maintainability", 
            "CA1506:AvoidExcessiveClassCoupling", 
            Justification = "This warning is triggered due to the seperate of string contants into seperate classes in seperate namespaces to represent their DTDs. It may be ignored.")]
        public XmlElement GetSignedXmlForCard()
        {
            XmlElement signedCard;

            using (MemoryStream stream = new MemoryStream())
            {
                using (XmlWriter writer = XmlWriter.Create(stream))
                {
                    if (null == writer)
                    {
                        throw new XmlException(Resources.ErrorCannotCreateXmlWriter);
                    }

                    writer.WriteStartElement(SharpSTS.Constants.WSIdentity.Elements.InformationCard, SharpSTS.Constants.WSIdentity.Namespace.Uri);

                    // Write the initial reference element
                    writer.WriteAttributeString(SharpSTS.Constants.Xml.Attributes.Language, SharpSTS.Constants.Xml.Namespace.Uri, "en-us");
                    writer.WriteStartElement(SharpSTS.Constants.WSIdentity.Elements.InformationCardReference, SharpSTS.Constants.WSIdentity.Namespace.Uri);
                    writer.WriteElementString(SharpSTS.Constants.WSIdentity.Elements.CardId, SharpSTS.Constants.WSIdentity.Namespace.Uri, this.Id);
                    writer.WriteElementString(
                        SharpSTS.Constants.WSIdentity.Elements.CardVersion, 
                        SharpSTS.Constants.WSIdentity.Namespace.Uri,
                        this.Version.ToString(CultureInfo.InvariantCulture));
                    writer.WriteEndElement();

                    // Card Name
                    if (!String.IsNullOrEmpty(this.Name))
                    {
                        writer.WriteStartElement(SharpSTS.Constants.WSIdentity.Elements.CardName, SharpSTS.Constants.WSIdentity.Namespace.Uri);
                        writer.WriteString(this.Name);
                        writer.WriteEndElement();
                    }

                    // Card Image
                    if (null != this.Image && 0 != this.Image.Length)
                    {
                        writer.WriteStartElement(SharpSTS.Constants.WSIdentity.Elements.CardImage, SharpSTS.Constants.WSIdentity.Namespace.Uri);
                        if (!String.IsNullOrEmpty(this.ImageMimeType))
                        {
                            writer.WriteAttributeString(SharpSTS.Constants.WSIdentity.Attributes.MimeType, this.ImageMimeType);
                        }

                        writer.WriteString(Convert.ToBase64String(this.Image));
                        writer.WriteEndElement();
                    }

                    // Card Issuer
                    writer.WriteStartElement(SharpSTS.Constants.WSIdentity.Elements.Issuer, SharpSTS.Constants.WSIdentity.Namespace.Uri);
                    writer.WriteString(STSSettings.Settings.Issuer.ToString());
                    writer.WriteEndElement();

                    // Creation and expiry times
                    writer.WriteStartElement(SharpSTS.Constants.WSIdentity.Elements.TimeIssued, SharpSTS.Constants.WSIdentity.Namespace.Uri);
                    writer.WriteString(XmlConvert.ToString(DateTime.Now, XmlDateTimeSerializationMode.Utc));
                    writer.WriteEndElement();
                    writer.WriteStartElement(SharpSTS.Constants.WSIdentity.Elements.TimeExpires, SharpSTS.Constants.WSIdentity.Namespace.Uri);
                    writer.WriteString(XmlConvert.ToString(this.ExpiryDateTime, XmlDateTimeSerializationMode.Utc));
                    writer.WriteEndElement();

                    this.WriteEndpointReferences(writer);
                    this.WriteUserCredentials(writer);
                    
                    writer.WriteEndElement(); // Close Token Service
                    writer.WriteEndElement(); // Close Token Service List

                    WriteSupportedTokenTypes(writer);

                    this.WriteSupportedClaims(writer);

                    // STS requires Endpoint information
                    if (STSSettings.Settings.RequireRelyingPartyAddress)
                    {
                        writer.WriteElementString(
                            SharpSTS.Constants.WSIdentity.Elements.RequireAppliesTo,
                            SharpSTS.Constants.WSIdentity.Namespace.Uri, 
                            null);
                    }

                    // Privacy Notice
                    if (!String.IsNullOrEmpty(STSSettings.Settings.PrivacyPolicyLocation))
                    {
                        writer.WriteStartElement(SharpSTS.Constants.WSIdentity.Elements.PrivacyNoticeAt, SharpSTS.Constants.WSIdentity.Namespace.Uri);
                        writer.WriteString(STSSettings.Settings.PrivacyPolicyLocation);
                        writer.WriteEndElement();
                    }

                    // STS requires HTTPS relying parties.
                    if (STSSettings.Settings.RequireSecureRelyingParty)
                    {
                        writer.WriteStartElement(
                            SharpSTS.Constants.WSIdentity2007.Namespace.Prefix,
                            SharpSTS.Constants.WSIdentity2007.Elements.RequireStrongRecipientIdentity,
                            SharpSTS.Constants.WSIdentity2007.Namespace.Uri);
                        writer.WriteEndElement();
                    }

                    WriteInformationEntries(writer);

                    writer.WriteEndElement();

                    writer.Close();

                    // Now sign the card
                    stream.Position = 0;

                    XmlDocument doc = new XmlDocument
                                          {
                                              PreserveWhitespace = false
                                          };
                    doc.Load(stream);
                    SignedXml signed = new SignedXml
                                           {
                                               SigningKey = SigningCertificate.PrivateKey
                                           };
                    signed.Signature.SignedInfo.CanonicalizationMethod
                        = SignedXml.XmlDsigExcC14NTransformUrl;

                    Reference reference = new Reference
                                              {
                                                  Uri = "#_Object_InfoCard"
                                              };
                    reference.AddTransform(new XmlDsigExcC14NTransform());
                    signed.AddReference(reference);

                    KeyInfo info = new KeyInfo();
                    KeyInfoX509Data data = new KeyInfoX509Data(
                        SigningCertificate,
                        X509IncludeOption.WholeChain);
                    info.AddClause(data);
                    signed.KeyInfo = info;

                    if (doc.DocumentElement == null)
                    {
                        throw new XmlException(Resources.ErrorSigningOfInformationCardFailed);
                    }

                    DataObject cardData = new DataObject("_Object_InfoCard", null, null, doc.DocumentElement);
                    signed.AddObject(cardData);
                    signed.ComputeSignature();
                    signedCard = signed.GetXml();                    
                }
                
                stream.Close();
            }

            return signedCard;
        }

        /// <summary>
        /// Writes the supported token types to the specified XML writer.
        /// </summary>
        /// <param name="writer">The writer to write the supported token types to..</param>
        private static void WriteSupportedTokenTypes(XmlWriter writer)
        {
            // Token Types
            writer.WriteStartElement(
                SharpSTS.Constants.WSIdentity.Elements.SupportedTokenTypeList, 
                SharpSTS.Constants.WSIdentity.Namespace.Uri);
                    
            foreach (Token token in STSSettings.Settings.Tokens)
            {
                writer.WriteElementString(
                    SharpSTS.Constants.WSTrust.Elements.TokenType,
                    SharpSTS.Constants.WSTrust.Namespace.Uri,
                    token.Uri);
            }

            writer.WriteEndElement();
        }

        /// <summary>
        /// Writes the information entries to the specified XML writer.
        /// </summary>
        /// <param name="writer">The XML writer write the information entries into.</param>
        private static void WriteInformationEntries(XmlWriter writer)
        {
            if (STSSettings.Settings.InformationEntries.Count > 0)
            {
                if (STSSettings.Settings.InformationEntries.Count > 20)
                {
                    throw new ConfigurationErrorsException(Resources.ErrorTooManyInformationEntries);
                }

                writer.WriteStartElement(
                    SharpSTS.Constants.WSIdentity2007.Elements.IssuerInformation,
                    SharpSTS.Constants.WSIdentity2007.Namespace.Uri);
                foreach (InformationEntry informationEntry in STSSettings.Settings.InformationEntries)
                {
                    if (informationEntry.Name.Length > 255 || informationEntry.Value.Length > 255)
                    {
                        throw new ConfigurationErrorsException(Resources.ErrorInformationEntryLength);
                    }

                    if (string.IsNullOrEmpty(informationEntry.Value))
                    {
                        continue;
                    }

                    writer.WriteStartElement(
                        SharpSTS.Constants.WSIdentity2007.IssuerInformation.IssuerInformationEntry.Elements.IssuerInformationEntry);
                    writer.WriteElementString(
                        SharpSTS.Constants.WSIdentity2007.IssuerInformation.IssuerInformationEntry.Elements.EntryName,
                        informationEntry.Name);
                    writer.WriteElementString(
                        SharpSTS.Constants.WSIdentity2007.IssuerInformation.IssuerInformationEntry.Elements.EntryValue,
                        informationEntry.Value);
                    writer.WriteEndElement();
                }

                writer.WriteEndElement();
            }
        }

        /// <summary>
        /// Writes the supported claims to the specified XML writer.
        /// </summary>
        /// <param name="writer">The writer to add the supported claims to.</param>
        private void WriteSupportedClaims(XmlWriter writer)
        {
            // Supported claims
            writer.WriteStartElement(SharpSTS.Constants.WSIdentity.Elements.SupportedClaimTypeList, SharpSTS.Constants.WSIdentity.Namespace.Uri);
            foreach (ManagedCardClaim claim in this.Claims)
            {
                writer.WriteStartElement(
                    SharpSTS.Constants.WSIdentity.Elements.SupportedClaimType,
                    SharpSTS.Constants.WSIdentity.Namespace.Uri);
                writer.WriteAttributeString(SharpSTS.Constants.WSIdentity.Attributes.Uri, claim.Uri);

                string claimDescription = claim.Description;
                if (String.IsNullOrEmpty(claimDescription))
                {
                    claimDescription = AuthorisationPolicyManager.GetDisplayName(claim.Uri);
                }

                if (!String.IsNullOrEmpty(claimDescription))
                {
                    writer.WriteElementString(
                        SharpSTS.Constants.WSIdentity.Elements.DisplayTag,
                        SharpSTS.Constants.WSIdentity.Namespace.Uri,
                        claimDescription);
                    writer.WriteElementString(
                        SharpSTS.Constants.WSIdentity.Elements.Description,
                        SharpSTS.Constants.WSIdentity.Namespace.Uri,
                        claimDescription);
                }

                writer.WriteEndElement();
            }

            writer.WriteEndElement();
        }

        /// <summary>
        /// Writes the endpoint references to the specified XML writer.
        /// </summary>
        /// <param name="writer">The writer to add the endpoint references to.</param>
        private void WriteEndpointReferences(XmlWriter writer)
        {
            // Endpoint references
            writer.WriteStartElement(SharpSTS.Constants.WSIdentity.Elements.TokenServiceList, SharpSTS.Constants.WSIdentity.Namespace.Uri);
            EndpointAddressBuilder eprBuilder = new EndpointAddressBuilder
                                                {
                                                    Uri = this.ServiceAddress,
                                                    Identity = new X509CertificateEndpointIdentity(SigningCertificate)
                                                };

            if (null != this.MexAddress)
            {
                MetadataReference mexRef = new MetadataReference
                                           {
                                               Address = new EndpointAddress(this.MexAddress),
                                               AddressVersion = AddressingVersion.WSAddressing10
                                           };

                MetadataSection mexSection = new MetadataSection
                                             {
                                                 Metadata = mexRef
                                             };

                MetadataSet mexSet = new MetadataSet();
                mexSet.MetadataSections.Add(mexSection);

                using (MemoryStream memStream = new MemoryStream())
                {
                    using (XmlTextWriter tokenWriter = new XmlTextWriter(memStream, Encoding.UTF8))
                    {
                        mexSet.WriteTo(tokenWriter);
                        tokenWriter.Flush();
                        memStream.Seek(0, SeekOrigin.Begin);
                        XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(
                            memStream,
                            XmlDictionaryReaderQuotas.Max);
                        eprBuilder.SetMetadataReader(reader);
                        tokenWriter.Close();                                
                    }

                    memStream.Close();
                }
            }

            EndpointAddress endpointList = eprBuilder.ToEndpointAddress();
            writer.WriteStartElement(SharpSTS.Constants.WSIdentity.Elements.TokenService, SharpSTS.Constants.WSIdentity.Namespace.Uri);
            endpointList.WriteTo(AddressingVersion.WSAddressing10, writer);
        }

        /// <summary>
        /// Writes the user credentials to the specified XML writer.
        /// </summary>
        /// <param name="writer">The writer to write the user credentials to.</param>
        private void WriteUserCredentials(XmlWriter writer)
        {
            // User Credentials
            writer.WriteStartElement(SharpSTS.Constants.WSIdentity.Elements.UserCredential, SharpSTS.Constants.WSIdentity.Namespace.Uri);
            if (!String.IsNullOrEmpty(this.CredentialDescription))
            {
                writer.WriteStartElement(
                    SharpSTS.Constants.WSIdentity.Elements.DisplayCredentialHint, 
                    SharpSTS.Constants.WSIdentity.Namespace.Uri);
                writer.WriteString(this.CredentialDescription);
                writer.WriteEndElement();
            }

            switch (this.Type)
            {
                case AuthenticationType.UserNamePassword:
                    writer.WriteStartElement(
                        SharpSTS.Constants.WSIdentity.Elements.UserNamePasswordCredential, 
                        SharpSTS.Constants.WSIdentity.Namespace.Uri);
                    if (!string.IsNullOrEmpty(this.CredentialHint))
                    {
                        writer.WriteStartElement(SharpSTS.Constants.WSIdentity.Elements.UserName, SharpSTS.Constants.WSIdentity.Namespace.Uri);
                        writer.WriteString(this.CredentialHint);
                        writer.WriteEndElement();
                    }

                    writer.WriteEndElement();
                    break;
                case AuthenticationType.Kerberos:
                    writer.WriteStartElement(
                        SharpSTS.Constants.WSIdentity.Elements.KerberosV5Credential,
                        SharpSTS.Constants.WSIdentity.Namespace.Uri);
                    writer.WriteEndElement();
                    break;
                case AuthenticationType.SelfIssued:
                    writer.WriteStartElement(
                        SharpSTS.Constants.WSIdentity.Elements.SelfIssuedCredential,
                        SharpSTS.Constants.WSIdentity.Namespace.Uri);
                    if (!string.IsNullOrEmpty(this.CredentialHint))
                    {
                        writer.WriteStartElement(
                            SharpSTS.Constants.WSIdentity.Elements.PrivatePersonalIdentifier,
                            SharpSTS.Constants.WSIdentity.Namespace.Uri);
                        writer.WriteString(this.CredentialHint);
                        writer.WriteEndElement();
                    }
                    else
                    {
                        throw new InvalidDataException(Resources.ErrorNoPPIDWasSpecified);
                    }

                    writer.WriteEndElement();
                    break;
                case AuthenticationType.Certificate:
                    writer.WriteStartElement(
                        SharpSTS.Constants.WSIdentity.Elements.X509V3Credential, 
                        SharpSTS.Constants.WSIdentity.Namespace.Uri);

                    writer.WriteStartElement(SharpSTS.Constants.XmlDSig.KeyInfo.Elements.X509Data, SharpSTS.Constants.XmlDSig.Namespace.Uri);
                    if (!string.IsNullOrEmpty(this.CredentialHint))
                    {
                        writer.WriteStartElement(
                            SharpSTS.Constants.WSSecurityExt.Elements.KeyIdentifier, 
                            SharpSTS.Constants.WSSecurityExt.NameSpace.Uri);
                        writer.WriteAttributeString(
                            SharpSTS.Constants.WSSecurityExt.Attributes.ValueType,
                            null,
                            SharpSTS.Constants.WSSecurityExt.KeyTypes.Sha1Thumbprint);
                        writer.WriteString(this.CredentialHint);
                        writer.WriteEndElement();
                    }
                    else
                        throw new InvalidDataException(Resources.ErrorNoCertificateThumbprintWasSpecified);

                    writer.WriteEndElement();
                    writer.WriteEndElement();
                    break;
                default:
                    break;
            }

            writer.WriteEndElement(); // Close User Credentials
        }

        #endregion
    }
}
